﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using JigLibX.Physics;
using JigLibX.Geometry;
using JigLibX.Collision;
using JigLibX.Math;
using JigLibX.Vehicles;

namespace LloydGame
{
    public class CarObject : GameObject
    {
        private LloydCar m_car;
        private bool m_controlled;
        private bool m_reverse_keys;
        private float m_speed;
        private bool topSpeed;
        private bool accelerate;
        private bool decelerate;
        private Cue m_Accelerate;
        private Cue m_TopSpeed;
        private Cue m_Decelerate;
        private Cue m_collision;
        private TimeSpan m_r_Time;

        #region Properties
        public LloydCar car
        {
            get { return m_car; }
        }

        public float speed
        {
            get { return m_speed; }
            set { m_speed = value; }
        }
        public bool reverse_keys
        {
            get { return m_reverse_keys; }
            set { m_reverse_keys = value; }
        }
        #endregion Properties

        public CarObject( Game p_game, GameWorld p_gameWorld, String p_objectType, Vector3 p_position
                         , Vector3 p_rotation, Vector3 p_scale, bool p_staticFlag, String p_alias, bool p_controlled) 
            : base(p_game, p_gameWorld, p_objectType, p_position, p_rotation, p_scale, p_staticFlag, p_alias)
        {
            m_Accelerate = ((Game1)Game).soundBank.GetCue("Accelerate");
            m_Decelerate = ((Game1)Game).soundBank.GetCue("Decelerate3");
            m_TopSpeed = ((Game1)Game).soundBank.GetCue("TopSpeed2");
            m_collision = ((Game1)Game).soundBank.GetCue("Krock_2g");
            topSpeed = false;
            accelerate = false;
            decelerate = false;
            m_r_Time = ((Game1)Game).gameTime.TotalRealTime;

            m_controlled = p_controlled;
            m_speed = 0.5f;
            m_reverse_keys = false;
            loadCar("GameContent\\Objects\\" + p_objectType + ".txt");
        }

        private void loadCar(String p_CarFile)
        {
            char[] charSeparators = new char[] { ' ', '\t' };

            try
            {
                using (StreamReader sr = new StreamReader(p_CarFile))
                {
                    String line;

                    // read the file line by line
                    while ((line = sr.ReadLine()) != null)
                    {
                        if (line.StartsWith("#")) continue; // Skip comments

                        string[] tokens = line.Split(charSeparators, System.StringSplitOptions.RemoveEmptyEntries);

                        if (tokens.Length == 0) continue; // Skip empty rows

                        // Load model files
                        if (line.StartsWith("chassi:"))
                        {
                            //GameModel gameModel = new GameModel(((Game1)Game), tokens[1], new Vector3(0.0f, 1.7f, 0.0f), Vector3.Zero, Vector3.One);
                            GameModel gameModel;
                            if (((Game1)Game).ee)
                                gameModel = new GameModel(((Game1)Game), "rc_car_model04", Vector3.Zero, Vector3.Zero, Vector3.One * 0.7f);
                            else
                                gameModel = new GameModel(((Game1)Game), tokens[1], Vector3.Zero, Vector3.Zero, Vector3.One * 0.7f);
                            //GameModel gameModel = new GameModel(((Game1)Game), tokens[1], new Vector3(0.0f, 1.25f, 0.0f), 
                            //    new Vector3(-MathHelper.PiOver2, MathHelper.PiOver2, 0.0f), Vector3.One * 0.8f);
                            gameModels.Add(gameModel);
                        }
                        else if (line.StartsWith("wheel:")) // adds a primitive to the collision skin
                        {
                            GameModel gameModel = new GameModel(((Game1)Game), tokens[1], Vector3.Zero, Vector3.Zero, Vector3.One);
                            gameModels.Add(gameModel);
                            gameModel = new GameModel(((Game1)Game), tokens[1], Vector3.Zero, Vector3.Zero, Vector3.One);
                            gameModels.Add(gameModel);
                            gameModel = new GameModel(((Game1)Game), tokens[1], Vector3.Zero, Vector3.Zero, Vector3.One);
                            gameModels.Add(gameModel);
                            gameModel = new GameModel(((Game1)Game), tokens[1], Vector3.Zero, Vector3.Zero, Vector3.One);
                            gameModels.Add(gameModel);

                        }
                        else if (line.StartsWith("parameters:"))
                        {
                            // float p_length, float p_width, float p_height, bool FWDrive, bool RWDrive, float maxSteerAngle, float steerRate,
                            // float wheelSideFriction,
                            // float wheelFwdFriction, float wheelTravel, float wheelRadius, float wheelZOffset, float wheelRestingFrac,
                            // float wheelDampingFrac, int wheelNumRays, float driveTorque, float gravity
                            m_car = new LloydCar(
                                float.Parse(tokens[1], CultureInfo.InvariantCulture.NumberFormat), // lenght
                                float.Parse(tokens[2], CultureInfo.InvariantCulture.NumberFormat), // width
                                float.Parse(tokens[3], CultureInfo.InvariantCulture.NumberFormat), // height
                                bool.Parse(tokens[4]),  // FWDrive
                                bool.Parse(tokens[5]),  // RWDrive
                                float.Parse(tokens[6], CultureInfo.InvariantCulture.NumberFormat), // maxSteerAngle
                                float.Parse(tokens[7], CultureInfo.InvariantCulture.NumberFormat), // steerRate
                                float.Parse(tokens[8], CultureInfo.InvariantCulture.NumberFormat), // wheelFSideFriction
                                float.Parse(tokens[9], CultureInfo.InvariantCulture.NumberFormat), // wheelRSideFriction
                                float.Parse(tokens[10], CultureInfo.InvariantCulture.NumberFormat), // wheelFwdFriction
                                float.Parse(tokens[11], CultureInfo.InvariantCulture.NumberFormat), // wheelRwdFriction
                                float.Parse(tokens[12], CultureInfo.InvariantCulture.NumberFormat), // wheelTravel
                                float.Parse(tokens[13], CultureInfo.InvariantCulture.NumberFormat), // wheelRadius
                                float.Parse(tokens[14], CultureInfo.InvariantCulture.NumberFormat), // wheelZOffset
                                float.Parse(tokens[15], CultureInfo.InvariantCulture.NumberFormat), // wheelRestingFrac
                                float.Parse(tokens[16], CultureInfo.InvariantCulture.NumberFormat), // wheelDampingFrac
                                int.Parse(tokens[17], CultureInfo.InvariantCulture.NumberFormat), // wheelNumRays
                                float.Parse(tokens[18], CultureInfo.InvariantCulture.NumberFormat), // topSpeed
                                float.Parse(tokens[19], CultureInfo.InvariantCulture.NumberFormat), // driveTorque
                                float.Parse(tokens[20], CultureInfo.InvariantCulture.NumberFormat)); // gravity
                            
                            m_car.Chassis.Body.MoveTo(m_position, Matrix.CreateFromYawPitchRoll(m_rotation.Y, m_rotation.X, m_rotation.Z));
                            m_body = m_car.Chassis.Body;
                            m_skin = m_car.Chassis.Skin;
                            SetCarMass(float.Parse(tokens[21], CultureInfo.InvariantCulture.NumberFormat));
                            m_car.EnableCar();
                            m_car.Chassis.Body.AllowFreezing = false;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("The file could not be read, or wasn't properly formated:");
                Console.WriteLine(e.Message);
            }
        }
        private void updateCues()
        {
            m_Accelerate = ((Game1)Game).soundBank.GetCue("Accelerate");
            m_Decelerate = ((Game1)Game).soundBank.GetCue("Decelerate3");
            m_TopSpeed = ((Game1)Game).soundBank.GetCue("TopSpeed2");
        }

        private void playCarSound (bool gasPressed)
        {
            if (gasPressed)
            {
                if (!accelerate && !topSpeed)
                {
                    updateCues();
                    m_Decelerate.Stop(AudioStopOptions.Immediate);
                    m_TopSpeed.Stop(AudioStopOptions.Immediate);
                    m_Accelerate.Play();
                    accelerate = true;
                }
                else if(accelerate)
                {
                    if(!m_Accelerate.IsPlaying)
                    {
                        updateCues();
                        m_TopSpeed.Play();
                        accelerate = false;
                        decelerate = false;
                        topSpeed = true;
                    }
                }
                else if (topSpeed)
                {
                    if (!m_TopSpeed.IsPlaying)
                    {
                        updateCues();
                        m_TopSpeed.Play();
                    }
                }
            }
            else
            {
                if (accelerate || topSpeed)
                {
                    m_Accelerate.Stop(AudioStopOptions.Immediate);
                    m_TopSpeed.Stop(AudioStopOptions.Immediate);
                    updateCues();
                    m_Decelerate.Play();
                }
                accelerate = false;
                topSpeed = false;
                decelerate = true;
                if (!m_Decelerate.IsPlaying)
                {
                    decelerate = false;
                }
            }
        }

        private void SetCarMass(float mass)
        {
            m_body.Mass = mass;
            Vector3 min, max;
            m_car.Chassis.GetDims(out min, out max);
            Vector3 sides = max - min;

            float Ixx = (1.0f / 12.0f) * mass * (sides.Y * sides.Y + sides.Z * sides.Z);
            float Iyy = (1.0f / 12.0f) * mass * (sides.X * sides.X + sides.Z * sides.Z);
            float Izz = (1.0f / 12.0f) * mass * (sides.X * sides.X + sides.Y * sides.Y);

            Matrix inertia = Matrix.Identity;
            inertia.M11 = Ixx; inertia.M22 = Iyy; inertia.M33 = Izz;
            m_car.Chassis.Body.BodyInertia = inertia;
            m_car.SetupDefaultWheels();
        }

        public override void Update(GameTime gameTime)
        {
            
            m_position = m_body.Position;

            Matrix o = m_skin.GetPrimitiveLocal(0).Transform.Orientation;
            Vector3 skin_rot = Vector3.Zero;
            m_rotation.X = (float)-Math.Asin(o.M32);
            m_rotation.Y = (float)Math.Atan2(o.M31, o.M33);
            m_rotation.Z = (float)Math.Atan2(o.M12, o.M22);
            skin_rot = m_rotation;

            o = m_body.Orientation;
            m_rotation.X += (float)-Math.Asin(o.M32);
            m_rotation.Y += (float)Math.Atan2(o.M31, o.M33);
            m_rotation.Z += (float)Math.Atan2(o.M12, o.M22);

            // Is this car controlled locally?
            if (!((Game1)Game).gameInitiated)
                m_car.HBrake = 1.0f;

            if (m_controlled && ((Game1)Game).gameInitiated)
            {
                KeyboardState keyState = UserInput.instance.keyboardState;

                if(keyState.IsKeyDown(Keys.R) && !UserInput.instance.prevKeyboardState.IsKeyDown(Keys.R) &&
                    (((Game1)Game).gameTime.TotalRealTime - m_r_Time > new TimeSpan(0,0,2)) && 
                        (Vector3.Transform(Vector3.Up, m_body.Orientation).Y < 0.0f))
                {
                    m_r_Time = ((Game1)Game).gameTime.TotalRealTime;
                    Random rand = new Random();
                    m_body.MoveTo(m_position + Vector3.Up * 3.0f, Matrix.CreateRotationY((float)rand.Next(0, 359)));
                }

                if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.Down))
                {
                    playCarSound(true);
                }
                else
                {
                    playCarSound(false);
                }

                if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.Down))
                {
                    if (m_reverse_keys)
                    {
                        if (keyState.IsKeyDown(Keys.Up))
                        {
                            m_car.Accelerate = -m_speed;
                        }
                        else
                        {
                            m_car.Accelerate = m_speed;
                        }
                    }
                    else
                    {
                        if (keyState.IsKeyDown(Keys.Up))
                        {
                            m_car.Accelerate = m_speed;
                        }
                        else
                        {
                            m_car.Accelerate = -m_speed;
                        }
                    }
                }
                else
                    m_car.Accelerate = 0.0f;

                if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.Right))
                {
                    if (reverse_keys)
                    {
                        if (keyState.IsKeyDown(Keys.Left))
                            m_car.Steer = -1.0f;
                        else
                            m_car.Steer = 1.0f;
                    }
                    else
                    {
                        if (keyState.IsKeyDown(Keys.Left))
                            m_car.Steer = 1.0f;
                        else
                            m_car.Steer = -1.0f;
                    }
                }
                else
                {
                    m_car.Steer = 0.0f;
                }

                // Hand break
                if (keyState.IsKeyDown(Keys.Space))
                {
                    m_car.HBrake = 1.0f;
                }
                else
                {
                    m_car.HBrake = 0.0f;
                }

                // "Standard" break
                if (keyState.IsKeyDown(Keys.B))
                {
                    m_car.SBreak = 1.0f;
                }
                else
                {
                    m_car.SBreak = 0.0f;
                }
            }

            //Update the wheel positions
            LloydWheel wh;

            wh = m_car.Wheels[0];
            float steer = wh.SteerAngle;
            //Vector3 wheel_pos = new Vector3(0.95f, 0.0f, -0.4f);
            m_gameModels[1].position = Vector3.Transform((wh.Pos + wh.Displacement * wh.LocalAxisUp), m_car.Chassis.Body.Orientation * m_car.Chassis.Skin.GetPrimitiveLocal(0).Transform.Orientation);
            m_gameModels[1].rotation = -skin_rot + new Vector3(0.0f, MathHelper.ToRadians(steer), MathHelper.ToRadians(wh.AxisAngle));

            wh = m_car.Wheels[1];
            steer = wh.SteerAngle;
            //wheel_pos = new Vector3(1.05f, 0.0f, -0.05f);
            m_gameModels[2].position = Vector3.Transform((wh.Pos + wh.Displacement * wh.LocalAxisUp), m_car.Chassis.Body.Orientation * m_car.Chassis.Skin.GetPrimitiveLocal(0).Transform.Orientation);
            m_gameModels[2].rotation = -skin_rot + new Vector3(0.0f, MathHelper.ToRadians(steer), MathHelper.ToRadians(wh.AxisAngle));

            wh = m_car.Wheels[2];
            steer = wh.SteerAngle;
            //wheel_pos = new Vector3(0.6f, 0.0f, 0.05f);
            m_gameModels[3].position = Vector3.Transform((wh.Pos + wh.Displacement * wh.LocalAxisUp), m_car.Chassis.Body.Orientation * m_car.Chassis.Skin.GetPrimitiveLocal(0).Transform.Orientation);
            m_gameModels[3].rotation = -skin_rot + new Vector3(0.0f, MathHelper.ToRadians(steer), MathHelper.ToRadians(-wh.AxisAngle));

            wh = m_car.Wheels[3];
            steer = wh.SteerAngle;
            //wheel_pos = new Vector3(1.05f, 0.0f, -0.05f);
            m_gameModels[4].position = Vector3.Transform((wh.Pos + wh.Displacement * wh.LocalAxisUp), m_car.Chassis.Body.Orientation * m_car.Chassis.Skin.GetPrimitiveLocal(0).Transform.Orientation);
            m_gameModels[4].rotation = -skin_rot + new Vector3(0.0f, MathHelper.ToRadians(steer), MathHelper.ToRadians(-wh.AxisAngle));

            // emit sparks where the car collides
            Vector3 carVelocity = (m_car.Chassis.Skin.NewPosition - m_car.Chassis.Skin.OldPosition) * ((Game1)Game).iterationsMade * 0.5f
                / ((gameTime.ElapsedGameTime.Milliseconds) / 1000.0f);

            foreach (CollisionInfo collInfo in m_body.CollisionSkin.Collisions)
            {
                if (collInfo != null)
                {
                    for (int i = 0; i < collInfo.NumCollPts; i++)
                    {
                        CollPointInfo collPointInfo = collInfo.PointInfo[i];

                        if (collPointInfo != null)
                        {
                            ((Game1)Game).gameWorld.sparkParticleSystem.spawnParticles((int)carVelocity.Length() * 2,
                                collPointInfo.Position, -carVelocity, null);
                        }
                    }
                }
            }
            
            if ((Math.Abs((m_car.Chassis.Body.Velocity - m_car.Chassis.Body.OldVelocity).Length()) > 1.0f) && !m_collision.IsPlaying)
            {
                m_collision = ((Game1)Game).soundBank.GetCue("Krock_3d");
                m_collision.Play();
                /*
                Random ran = new Random();
                switch (ran.Next(1, 2))
                {
                    case 1:
                        m_collision = ((Game1)Game).soundBank.GetCue("Krock_2g");
                        m_collision.Play();
                        break;
                    case 2:
                        m_collision = ((Game1)Game).soundBank.GetCue("Krock_3d");
                        m_collision.Play();
                        break;
                }*/
            }
            
            base.Update(gameTime);
        }
    }
}
